基本上 HTTP 是沒有紀錄狀態的協定,但可以透過 Cookies 將 Request 來源區分出來,並將部分資料暫存於 Cookies 及 Session,是寫網站常用的用戶資料暫存方式。
本篇將介紹如何在 ASP.NET Core 使用 Cookie 及 Session。
同步發佈至個人部落格:
John Wu's Blog - [鐵人賽 Day11] ASP.NET Core 2 系列 - Cookies & Session
Cookies 是將用戶資料存在 Client 的瀏覽器,每次 Request 都會把 Cookies 送到 Server。
在 ASP.NET Core 中要使用 Cookie,可以透過 HttpContext.Request
及 HttpContext.Response
存取:
Startup.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
namespace MyWebsite
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
}
public void Configure(IApplicationBuilder app)
{
app.Run(async (context) =>
{
string message;
if (!context.Request.Cookies.TryGetValue("Sample", out message))
{
message = "Save data to cookies.";
}
context.Response.Cookies.Append("Sample", "This is Cookies.");
// 刪除 Cookies 資料
//context.Response.Cookies.Delete("Sample");
await context.Response.WriteAsync($"{message}");
});
}
}
}
從 HTTP 可以看到傳送跟收到的 Cookies 資訊:
當存在 Cookies 的資料越多,封包就會越大,因為每個 Request 都會帶著 Cookies 資訊。
Session 是透過 Cookies 內的唯一識別資訊,把用戶資料存在 Server 端記憶體、NoSQL 或資料庫等。
要在 ASP.NET Core 使用 Session 需要先加入兩個服務:
IDistributedCache
物件,讓 Session 服務知道要將 Session 存在哪邊。IDistributedCache
分散式快取)
Startup.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
namespace MyWebsite
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// 將 Session 存在 ASP.NET Core 記憶體中
services.AddDistributedMemoryCache();
services.AddSession();
}
public void Configure(IApplicationBuilder app)
{
// SessionMiddleware 加入 Pipeline
app.UseSession();
app.Run(async (context) =>
{
context.Session.SetString("Sample", "This is Session.");
string message = context.Session.GetString("Sample");
await context.Response.WriteAsync($"{message}");
});
}
}
}
HTTP Cookies 資訊如下:
可以看到多出了 .AspNetCore.Session
,.AspNetCore.Session
就是 Session 的唯一識別資訊。
每次 Request 時都會帶上這個值,當 Session 服務取得這個值後,就會去 Session 容器找出專屬這個值的 Session 資料。
以前 ASP.NET 可以將物件型別直接存放到 Session,現在 ASP.NET Core Session 不再自動序列化物件到 Sesson。
如果要存放物件型態到 Session 就要自己序列化了,這邊以 JSON 格式作為範例:
Extensions\SessionExtensions.cs
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
namespace MyWebsite.Extensions
{
public static class SessionExtensions
{
public static void SetObject<T>(this ISession session, string key, T value)
{
session.SetString(key, JsonConvert.SerializeObject(value));
}
public static T GetObject<T>(this ISession session, string key)
{
var value = session.GetString(key);
return value == null ? default(T) : JsonConvert.DeserializeObject<T>(value);
}
}
}
透過上例擴充方法,就可以將物件存取至 Session,如下:
using MyWebsite.Extensions;
// ...
var user = context.Session.GetObject<UserModel>("user");
context.Session.SetObject("user", user);
雖然 Session 資料都存在 Server 端看似安全,但如果封包被攔截,只要拿到 .AspNetCore.Session
就可以取到該用戶資訊,也是有風險。
有些安全調整建議實作:
.AspNetCore.Session
可以改掉。// ...
public void ConfigureServices(IServiceCollection services)
{
services.AddDistributedMemoryCache();
services.AddSession(options =>
{
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
options.Cookie.Name = "mywebsite";
options.IdleTimeout = TimeSpan.FromMinutes(5);
});
}
由於 Cookies 及 Session 預設都是使用字串的方式存取資料,弱型別無法在開發階段判斷有沒有打錯字,還是建議包裝成強型別比較好。
而且直接存取 Cookies/Session 的話邏輯相依性太強,對單元測試很不友善,所以還是建議包裝一下。
Wappers\SessionWapper.cs
using Microsoft.AspNetCore.Http;
using MyWebsite.Extensions;
// ...
public interface ISessionWapper
{
UserModel User { get; set; }
}
public class SessionWapper : ISessionWapper
{
private static readonly string _userKey = "session.user";
private readonly IHttpContextAccessor _httpContextAccessor;
public SessionWapper(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
private ISession Session
{
get
{
return _httpContextAccessor.HttpContext.Session;
}
}
public UserModel User
{
get
{
return Session.GetObject<UserModel>(_userKey);
}
set
{
Session.SetObject(_userKey, value);
}
}
}
在 DI 容器中加入 IHttpContextAccessor
及 ISessionWapper
,如下:
Startup.cs
// ...
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton<ISessionWapper, SessionWapper>();
}
IHttpContextAccessor
,讓 HttpContext
可以輕鬆的注入給需要用到的物件使用。IHttpContextAccessor
只是取用 HttpContext
實例的接口,用 Singleton 的方式就可以供其它物件使用。在 Controller 就可以直接注入 ISessionWapper
,以強型別的方式存取 Session,如下:
Controllers/HomeController.cs
using Microsoft.AspNetCore.Mvc;
using MyWebsite.Wappers;
namespace MyWebsite.Controllers
{
public class HomeController : Controller
{
private readonly ISessionWapper _sessionWapper;
public HomeController(ISessionWapper sessionWapper)
{
_sessionWapper = sessionWapper;
}
public IActionResult Index()
{
var user = _sessionWapper.User;
_sessionWapper.User = user;
return Ok(user);
}
}
}
Introduction to session and application state in ASP.NET Core
我又來了(?),最後有個錯字(部落格也是錯的)
在 Controller 就可以直接注入 ISessionWapper,以強行別的方式存取 Session
=> 以強型別的方式存取 Session
感謝~~
邦主您好,感謝您優質的文章,給予我莫大的幫助,文中的類別名稱 SessionWapper 是否調整為 SessionWrapper 較合適呢?給您做個參考。